#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>           /* For O_* constants */
#include <unistd.h>
#include <assert.h>
#include <string.h>
#include <time.h>

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>

#include "SharedMemory.h"

struct SM_t
{
    void*   m_shmem ;       /*  Pointer for the shared memory       */
    int     m_shmid;        /*  Shared memory id                    */
    int     m_semid;        /*  Semaphore id                        */
};

/*  union for semaphore */
union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };

/*******************************************************************************************************************************************/
/****************************************************    Internal Functions     ************************************************************/
/*******************************************************************************************************************************************/

/********************** Forward Declerations ************************************************/
/*  Function for allocating the SM struct and init is fields. The function creates a file in tmp directory in order  *
 *  to use ftok for shmid and semid. Open shared memory (according to _flags) attach to memory to get pointer and    *
 *  also open semaphore. init the SM struct with the fields for shmem and sem and return the struct                 */
static SM* AllocateAndInitSMStruct(const char* _memName, size_t _size, int _shmflags);
/********************************************************************************************/

static SM* AllocateAndInitSMStruct(const char* _memName, size_t _size, int _shmflags)
{
    key_t   key     ;
    FILE*   fp = 0  ;
    SM*     sm = 0  ;
    char*   path = (char*)malloc(sizeof(char) * (strlen(_memName) + strlen("/tmp/")+ 1) );

    if(!path)                                                /*  Check allocation            */
    {
        fprintf(stderr, "error in shMem_name allocation\n");
        return 0;
    }

    strcpy(path, "/tmp/");
    strcat(path, _memName);
    fp = fopen(path, "w");
    if(!fp)
    {
        perror("fopen");
        goto cleanFopen;
    }
    fclose(fp);

    sm = (SM*)malloc(sizeof(SM));
    if(!sm)                                                             /*  Error in allocation                         */
    {
        fprintf(stderr, "error in allocate shared memory struct\n");
        goto cleanFopen;
    }

    key = ftok(path, 'A');
    if(key == -1)
    {
        perror("ftok for shared memory");
        goto cleanFtok;
    }

    sm->m_shmid = shmget(key,           /*  unique key to shared memory                                             */
                        _size,          /*  size  equal to the value of _size rounded up to a multiple of PAGE_SIZE */
                        _shmflags);     /*  shared memory flags. IPC_CREAT - create, IPC_EXCL - exclusive creation  */
    if(sm->m_shmid == -1)
    {
        perror("shmget");
        goto cleanFtok;
    }

    sm->m_shmem = shmat(sm->m_shmid,/*  Shared memory id                                                        */
                        0,          /*  The address in virtual memory to attach the segment                     */
                        0);         /*  Flags of permisiions to access shared memory, if 0 => Read and Write    */
    if(sm->m_shmem == (void*)-1)
    {
        perror("shmat");
        goto cleanShmat;
    }

    key = ftok(path, 'B');
    if(key == -1)
    {
        perror("ftok for semaphore");
        goto cleanShmat;
    }

    sm->m_semid = semget( key  ,        /*  unique key to semaphore             */
                          1    ,        /*  number of semaphores in the set     */
                          _shmflags);   /* Flags of permissions and creation    */
    if(sm->m_semid == -1)
    {
        perror("semget");
        goto cleanShmat;
    }
    free(path);

    return sm;

    cleanShmat:
        shmctl(sm->m_shmid, IPC_RMID, 0);       /*  Remove the shared memory created by shmget  */
    cleanFtok:
        free(sm);
    cleanFopen:
        free(path);
        return 0;
}
/***********************************************************************************************************************/

SM* SMCreate(size_t _size, const char* _memName)
{
    SM*  sm;

    assert(_memName);
    assert(!strchr(_memName,'/'));

    sm = AllocateAndInitSMStruct(_memName, _size, 0644 | IPC_CREAT | IPC_EXCL);
    if(!sm)
    {
        return 0;
    }

    if(semctl(sm->m_semid, 0, SETVAL, 1) == -1) /*  Init semaphore  with the value 1    */
    {
        perror("init semaphore");
        free(sm);
        return 0;
    }

    return sm;
}

SM* SMAttach(const char* _memName)
{
    SM*         sm   = 0;

    assert(_memName);
    assert(!strchr(_memName,'/'));

    sm = AllocateAndInitSMStruct(_memName, 0, 0644);
    if(!sm)
    {
        return 0;
    }

    return sm;
}

void* SMGet(SM* _sm)
{
    assert(_sm);
    assert(_sm->m_shmem);

    return _sm->m_shmem;
}


int SMLockTimeOut(SM* _sm, unsigned long _timeout)
{
    struct timespec absTime;
    struct sembuf   sop;

    assert(_sm);
    assert(_sm->m_shmem);

    sop.sem_num = 0;
    sop.sem_flg = 0;
    sop.sem_op = -1;                        /*  decrement semaphore by 1                            */

    if(!_timeout)                           /*  If the timeout given 0, lock until sem is unlocked  */
    {
        return semop(_sm->m_semid,          /*  semaphore id        */
                     &sop,                  /*  options struct      */
                     1);                    /*  number of structs   */
    }

    absTime.tv_sec = _timeout/1000;         /*  Add the apropriate seconds            */
    absTime.tv_nsec = (_timeout%1000)*1000; /*  Add the apropriate nano seconds                     */

    return semtimedop(_sm->m_semid, &sop, 1, &absTime); /*  giving also the timespec struct */
}

int SMLock(SM* _sm)
{
    return SMLockTimeOut(_sm, 0);
}

int SMUnlock(SM* _sm)
{
    struct sembuf sop;
    pid_t  pid;

    assert(_sm);
    assert(_sm->m_shmem);

    pid = semctl(_sm->m_semid, 0, GETPID, 0);
    if(pid == -1)
    {
        perror("semcl_pid");
    }

    if(pid != getpid())         /*  make the semaphore beheave like a mutex */
    {
        perror("Try to unlock with other process");
        return -1;
    }

    sop.sem_num = 0;
    sop.sem_flg = 0;
    sop.sem_op  = 1;

    return semop(_sm->m_semid, &sop, 1);
}


void SMDetach(SM* _sm)
{
    struct shmid_ds buf;

    assert(_sm);
    assert(_sm->m_shmem);

    assert(!shmdt(_sm->m_shmem));           /* deattach the memory in virtual memory  */
    assert(!shmctl(_sm->m_shmid, IPC_STAT, &buf));

    /*  If the counter down to zero -> unlink the shared memory name    */
    if(!buf.shm_nattch)
    {
        assert(!shmctl(_sm->m_shmid, IPC_RMID, 0));
        assert(!semctl(_sm->m_semid, IPC_RMID, 0));
    }
    free(_sm);
}

int SMForceRemove(const char* _memName)
{
    int     shmid       ;
    int     semid       ;
    char    path[255] = "/tmp/";
    key_t   key;

    strcat(path, _memName);
    printf("%s\n", path);
    key = ftok(path, 'A');
    if(key == -1)
    {
        perror("ftok in main");
        return 1;
    }

    shmid = shmget(key, 0, 0644);
    if(shmid == -1)
    {
        perror("shmid");
    }
    if(shmctl(shmid, IPC_RMID, 0))
    {
        perror("shmctl");
    }
    key = ftok(path, 'B');
    semid = semget(key, 0, 0644);
    if(semid == -1)
    {
        perror("semget");
        return 1;
    }

    if(semctl(semid, IPC_RMID, 0))
    {
        perror("semctl");
        return 1;
    }

    return 0;
}

/***************************************************************************************************************/
/*********************************************    SM_UTEST    **************************************************/
/***************************************************************************************************************/
#ifdef SM_UTEST

#define IS_SUCCESS(X) !X?"SUCCESS":"FAILURE"   /*  Macro to print on regression tests       */
#define IF_FAIL(X) if(X){printf("FAIL IN %s TEST, IN LINE %d\n",#X,__LINE__); return 1;}
/*  Internal basic check of the parameters      */
/*  Wide test is done outsize with 3 procceses  */
int main()
{

    SM*     sm      = 0 ;
    SM*     sm1     = 0 ;
    int     size    = 50;
    void*   memory      ;
    void*   memory1     ;

    SMForceRemove("test");

    sm = SMCreate(size, "test");
    sm1 = SMAttach("test");

    IF_FAIL(sm->m_shmid != sm->m_shmid)
    IF_FAIL(sm->m_shmem != sm->m_shmem)

    memory = SMGet(sm);
    IF_FAIL((char*)sm->m_shmem != (char*)memory )
    strcpy((char*)memory, "This is a test");

     if(!sm1)
    {
        printf("error\n");
    }

    printf("Locking operation: %d\n", SMLockTimeOut(sm, 10));
    printf("Locking operation: %d\n", SMLockTimeOut(sm, 3000));
    printf("UnLocking operation: %d\n", SMUnlock(sm));

    memory1 = SMGet(sm1);

    IF_FAIL(sm1->m_shmid != sm1->m_shmid)
    IF_FAIL(sm1->m_semid != sm1->m_semid)
    IF_FAIL(strcmp(memory1, "This is a test"))

    SMDetach(sm);
    SMDetach(sm1);

    return 0;
}

#endif  /*  SM_UTEST    */
